package cn.lg.soar.core.util.easyexcel;

import cn.lg.soar.common.util.DatetimeUtil;
import cn.lg.soar.common.util.data.StringUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * easyexcel 工具
 * @author luguoxiang
 * @date 2021-06-18
 * @Dep: excel 工具
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class ExcelUtil {

    protected static final Logger LOGGER = LoggerFactory.getLogger(ExcelUtil.class);

    /**
     * 导出Excel
     * @param filename
     * @param data
     * @param resp
     * @param <E>
     */
    public static <E>void exportExcel(String filename, List<E> data, HttpServletResponse resp){
        if (StringUtil.isBlank(filename)) {
            filename = DatetimeUtil.ymdhms();;
        }
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("application/vnd.ms-excel");
        try (ServletOutputStream out = resp.getOutputStream()){
            resp.setHeader(
                    "Content-disposition",
                    "attachment;filename=" + URLEncoder.encode(filename, "UTF-8") + ExcelTypeEnum.XLSX.getValue()
            );
            ExcelUtil.write(out).sheet("sheet", data).execute();
        } catch (IOException e) {
            LOGGER.error("导出excel异常", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 导入 Excel
     * @param req
     * @param tClass
     * @param <E>
     * @return
     */
    public static <E>List<E> importExcel(HttpServletRequest req, Class<E> tClass) {
        try(ServletInputStream inputStream = req.getInputStream()) {
            return ExcelUtil.doRead(inputStream, 0, tClass);
        } catch (IOException e) {
            LOGGER.error("导入excel异常", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 导出单个 sheet
     * @param pathName 输出文件路径
     * @param sheetName sheet
     * @param data 源数据
     */
    public static <T>void doWrite(String pathName, String sheetName, List<T> data) {
        ExcelWriterSheetBuilder sheet = EasyExcel.write(pathName).sheet(sheetName);
        if (!CollectionUtils.isEmpty(data)) {
            sheet.head(data.get(0).getClass());
        }
        sheet.doWrite(data);
    }

    /**
     * 导出单个 sheet
     * @param stream 输出流
     * @param sheetName sheet名称
     * @param data 数据
     * @param <T>
     */
    public static <T>void doWrite(OutputStream stream, String sheetName, List<T> data) {
        ExcelWriterSheetBuilder sheet = EasyExcel.write(stream).sheet(sheetName);
        if (!CollectionUtils.isEmpty(data)) {
            sheet.head(data.get(0).getClass());
        }
        sheet.doWrite(data);
    }

    /**
     * 导出单个 sheet
     * @param pathName 输出文件路径
     * @param sheetNo sheet序号
     * @param data 数据
     * @param <T>
     */
    public static <T>void doWrite(String pathName, Integer sheetNo, List<T> data) {
        ExcelWriterSheetBuilder sheet = EasyExcel.write(pathName).sheet(sheetNo);
        if (!CollectionUtils.isEmpty(data)) {
            sheet.head(data.get(0).getClass());
        }
        sheet.doWrite(data);
    }

    /**
     * 导出单个 sheet
     * @param stream 输出流
     * @param sheetNo sheet序号
     * @param data 数据
     * @param <T>
     */
    public static <T>void doWrite(OutputStream stream, Integer sheetNo, List<T> data) {
        ExcelWriterSheetBuilder sheet = EasyExcel.write(stream).sheet(sheetNo);
        if (!CollectionUtils.isEmpty(data)) {
            sheet.head(data.get(0).getClass());
        }
        sheet.doWrite(data);
    }



    /**
     * 导入单 sheet
     * @param pathName 源文件路径
     * @param sheetName sheet名称
     * @param clazz java数据类型
     * @return
     */
    public static <T> List<T> doRead(String pathName, String sheetName, Class<T> clazz) {
        return EasyExcel.read(pathName).sheet(sheetName).head(clazz).doReadSync();
    }

    /**
     * 导入单 sheet
     * @param stream 输入流
     * @param sheetName sheet名称
     * @param clazz java数据类型
     * @param <T>
     * @return
     */
    public static <T>List<T> doRead(InputStream stream, String sheetName, Class<T> clazz) {
        return EasyExcel.read(stream).sheet(sheetName).head(clazz).doReadSync();
    }

    /**
     * 导入单 sheet
     * @param pathName 源文件路径
     * @param sheetNo sheet序号
     * @param clazz java数据类型
     * @param <T>
     * @return
     */
    public static <T>List<T> doRead(String pathName, Integer sheetNo, Class<T> clazz) {
        return EasyExcel.read(pathName).sheet(sheetNo).head(clazz).doReadSync();
    }

    /**
     * 导入单 sheet
     * @param stream 输入流
     * @param sheetNo sheet序号
     * @param clazz java数据类型
     * @param <T>
     * @return
     */
    public static <T>List<T> doRead(InputStream stream, Integer sheetNo, Class<T> clazz) {
        return EasyExcel.read(stream).sheet(sheetNo).head(clazz).doReadSync();
    }

    /**
     * 支持导出多个 sheet
     * @param pathName 目标文件路径
     * @return
     */
    public static Export write(String pathName) {
        return new Export(EasyExcel.write(pathName).build());
    }

    /**
     * 支持导出多个 sheet
     * @param out 目标输出流
     * @return
     */
    public static Export write(OutputStream out) {
        return new Export(EasyExcel.write(out).build());
    }

    /**
     * 支持导入多个 sheet
     * @param pathName 源文件路径
     * @return
     */
    public static Import read(String pathName) {
        return new Import(EasyExcel.read(pathName).build());
    }

    /**
     * 支持导入多个 sheet
     * @param stream 输入流
     * @return
     */
    public static Import read(InputStream stream) {
        return new Import(EasyExcel.read(stream).build());
    }

    /**
     * 多个 sheet 导出支持工具类
     */
    public static class Export {
        private final ExcelWriter writer;
        private final List<Sheet> sheets = new ArrayList<>();

        public Export(ExcelWriter writer) {
            this.writer = writer;
        }

        /**
         * 添加 sheet
         * @param sheetName
         * @param data
         * @param <T>
         * @return
         */
        public <T>Export sheet(String sheetName, List<T> data) {
            if (CollectionUtils.isEmpty(data)) {
                return this;
            }
            List<T> collect = data.stream().filter(Objects::nonNull).limit(1).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(collect)) {
                return this;
            }
            WriteSheet sheet = EasyExcel.writerSheet(sheetName)
                    .head(collect.get(0).getClass())
                    .build();
            this.sheets.add(new Sheet(sheet, data));
            return this;
        }

        /**
         * 执行导出
         */
        public void execute() {
            this.sheets.forEach(x -> this.writer.write(x.data, x.sheet));
            this.writer.finish();
        }

        class Sheet {
            private final WriteSheet sheet;
            private final List data;

            public Sheet(WriteSheet sheet, List data) {
                this.sheet = sheet;
                this.data = data;
            }
        }
    }

    /**
     * 多个 sheet 导入支持工具类
     */
    public static class Import {
        private final ExcelReader reader;
        private final List<ReadSheet> sheets = new ArrayList<>();

        public Import(ExcelReader reader) {
            this.reader = reader;
        }

        /**
         * 添加 sheet
         * @param sheetName
         * @param tClass
         * @param readListener
         * @param <T>
         * @return
         */
        public <T>Import sheet(String sheetName, Class<T> tClass, ReadListener<T> readListener) {
            ReadSheet build = EasyExcel.readSheet(sheetName).head(tClass).registerReadListener(readListener).build();
            this.sheets.add(build);
            return this;
        }

        /**
         * 添加 sheet
         * @param sheetNo
         * @param tClass
         * @param readListener
         * @param <T>
         * @return
         */
        public <T>Import sheet(Integer sheetNo, Class<T> tClass, ReadListener<T> readListener) {
            ReadSheet build = EasyExcel.readSheet(sheetNo).head(tClass).registerReadListener(readListener).build();
            this.sheets.add(build);
            return this;
        }

        /**
         * 添加 sheet
         * @param sheetName
         * @param tClass
         * @param consumer
         * @param <T>
         * @return
         */
        public <T>Import sheet(String sheetName, Class<T> tClass, Consumer<List<T>> consumer) {
            AnalysisEventListener<T> listener = new AnalysisEventListener<T>() {
                private final List<T> list = new ArrayList<>();

                @Override
                public void invoke(T data, AnalysisContext analysisContext) {
                    this.list.add(data);
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                    consumer.accept(this.list);
                }
            };
            ReadSheet build = EasyExcel.readSheet(sheetName).head(tClass).registerReadListener(listener).build();
            this.sheets.add(build);
            return this;
        }

        /**
         * 添加 sheet
         * @param sheetNo
         * @param tClass
         * @param consumer
         * @param <T>
         * @return
         */
        public <T>Import sheet(Integer sheetNo, Class<T> tClass, Consumer<List<T>> consumer) {
            AnalysisEventListener<T> listener = new AnalysisEventListener<T>() {
                private final List<T> list = new ArrayList<>();

                @Override
                public void invoke(T data, AnalysisContext analysisContext) {
                    this.list.add(data);
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                    consumer.accept(this.list);
                }
            };
            ReadSheet build = EasyExcel.readSheet(sheetNo).head(tClass).registerReadListener(listener).build();
            this.sheets.add(build);
            return this;
        }

        /**
         * 执行导入
         */
        public void execute() {
            this.reader.read(this.sheets);
            this.reader.finish();
        }
    }

}
